package com.polito.mad.bikeponyapp.fragments;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.maps.android.PolyUtil;
import com.google.maps.android.ui.BubbleIconFactory;
import com.polito.mad.bikeponyapp.BikePonyApplication;
import com.polito.mad.bikeponyapp.activity.MainActivity.ServiceStateChangeObserver;
import com.polito.mad.bikeponyapp.location.LocationObserver;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem;
import com.polito.mad.bikeponyapp.persistence.DeliveryItem.DeliveryItemType;
import com.polito.mad.bikeponyapp.service.ServerCommunicationService;
import com.polito.mad.bikeponyapp.util.DeliveriesFacade;

public class DeliveryMapFragment extends SupportMapFragment implements LocationObserver ,ServiceStateChangeObserver,OnMarkerClickListener{
	
	private static final String LNG_PARAM_NAME = "lng";
	private static final String LAT_PARAM_NAME = "lat";
	private GoogleMap map;
	private List<MarkerOptions> markers;
	private List<PolylineOptions> paths;
	private LatLng lastCurrentPosition;
	private boolean followAgentPosition;
	private volatile MarkerOptions currentPositionMarker = null;
	private BubbleIconFactory mBubbleIconFactory;
	private ServerCommunicationService service;
	private DeliveriesFacade deliveriesFacade;
	private boolean isPathDrawn=false;
	private MapsUpdateAsyncTask mapsUpdateAsyncTask;
	private Map<String,Integer> markerItemsMap;
	
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if(savedInstanceState!=null)
			lastCurrentPosition = new LatLng(savedInstanceState.getDouble(LAT_PARAM_NAME),savedInstanceState.getDouble(LNG_PARAM_NAME));

		map = getMap();
		
		/* init the objects */
		if (map != null)
			map.setMyLocationEnabled(true);
		if(markers==null)
			markers = new ArrayList<MarkerOptions>();
		if(markerItemsMap==null)
			markerItemsMap = new HashMap<String,Integer>();
		if(paths==null)
			paths = new ArrayList<PolylineOptions>();
		
		followAgentPosition=false;
		mBubbleIconFactory = new BubbleIconFactory(getActivity());
		
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		/*use the deliveryFacade to get items to show */
		this.deliveriesFacade = (DeliveriesFacade)activity;
	}

	@Override
	public void onSaveInstanceState(Bundle outState) {
		if(lastCurrentPosition!=null)
		{
			outState.putDouble(LAT_PARAM_NAME, lastCurrentPosition.latitude);
			outState.putDouble(LNG_PARAM_NAME, lastCurrentPosition.longitude);
		}
		super.onSaveInstanceState(outState);
	}
	@Override
	public void onStart() {
		super.onStart();
		getView().setBackgroundColor(0xffffff);
		map = getMap();
		if (map != null) {
			map.setMapType(GoogleMap.MAP_TYPE_NORMAL);
			map.setOnMarkerClickListener(this);
			map.setMyLocationEnabled(true);
			map.clear();
			displayItems();
		}
	}
	
	@Override
	public void onStop() {
		super.onStop();
		isPathDrawn=false;
	}

	@Override
	public synchronized void updateLocation(Location location) 
	{
		this.lastCurrentPosition = new LatLng(location.getLatitude(),location.getLongitude());
		if(isResumed())
		{
			displayItems();
			//createCurrentPositionMarker();			
		}
		/*
			startUpdateMap(deliveriesFacade.getItems(), -1,false);*/
	}

	private void updateMap(List<DeliveryItem> items,boolean redraw) {
		createCurrentPositionMarker();
		if (items != null) {
			createPath(items , redraw);
		}
	}

	/**
	 * 
	 */
	private void displayItems()
	{
		if(lastCurrentPosition==null)
		{
			Toast.makeText(getActivity(), "Waiting for the GPS position", Toast.LENGTH_LONG).show();
			return;
		}else if(!isPathDrawn)
		{
			isPathDrawn=true;
			if(mapsUpdateAsyncTask==null || mapsUpdateAsyncTask.getStatus()==AsyncTask.Status.FINISHED)
			{
				mapsUpdateAsyncTask = new MapsUpdateAsyncTask(deliveriesFacade.getItems(),deliveriesFacade.getItemSelected(),deliveriesFacade.isDirty());
				mapsUpdateAsyncTask.execute();
			}
		}
	}
	
	/**
	 * This method builds the Markers to be shown inside the map : it creates Icon, InfoWindow based on the DeliveryItem content 
	 * and places all those items on the map
	 * If the {@link redraw} is true , the markers are recomputed and reinerted , otherwise the cached ones are used
	 * @param index 
	 * @param zoom 
	 * @param mapCenter 
	 * @param items
	 * @param redraw
	 */
	private void showMarkers(LatLng mapCenter, int zoom, int index, List<DeliveryItem> items,boolean redraw) {
		
		/*
		 * add the markers to the map 
		 * and animate the camera to the center specified or use one of the created markers as the point , the first marker
		 * */
		if(index==-1)
			index=0;// show the first item
		
		for(int i=0;i<markers.size();i++)
		{
			MarkerOptions opt = markers.get(i);
			Marker m = map.addMarker(opt);
			/*use the MarkerItem map to know to which item corresponds each Marker when clicked*/
			markerItemsMap.put(m.getId(), i);
			if(i==index)
				m.showInfoWindow();
		}
		if(mapCenter==null && !markers.isEmpty())
			mapCenter = markers.get(0).getPosition();
		
		if(mapCenter!=null)
		{
			CameraUpdate newLatLngZoom = CameraUpdateFactory.newLatLngZoom(mapCenter, zoom);
			map.animateCamera(newLatLngZoom);
		}
	}
	
	private void prepareMarkers(LatLng mapCenter, int zoom, int index, List<DeliveryItem> items,boolean redraw)
	{
		if (items != null && redraw )
			markers.clear();
		else if(items==null || (!redraw && !markers.isEmpty()))
			return;
		
		/*
		 * Creating the Markers List in this way we are maintaining the same order of the Items List 
		 * so that we can easily manage the markers later in timt with respect to the bound item
		 */
		for (int i = 0; i < items.size(); i++) {
			
			DeliveryItem item = items.get(i);
			LatLng latLng = new LatLng(item.getLatitutde(), item.getLongitude());

			if (item.getDeliveryItemType() == DeliveryItemType.PICKUP)
				mBubbleIconFactory.setStyle(BubbleIconFactory.Style.GREEN);
			else
				mBubbleIconFactory.setStyle(BubbleIconFactory.Style.ORANGE);
			Bitmap iconBitmap = mBubbleIconFactory.makeIcon(String.valueOf(item
					.getOrder()));
			markers.add(new MarkerOptions()
			.position(latLng)
			.title(item.getAddress())
			.snippet(
					"to "+ item.getDeliveryItemType().toString()
					+ "  at  :"
					+ SimpleDateFormat.getDateTimeInstance().format(new Date(item.getEstimatedTime())))
			.icon(BitmapDescriptorFactory.fromBitmap(iconBitmap)));
		}
	}
	
	private void createPath(List<DeliveryItem> items , boolean rebuild) {
		if(items==null)
			return;
		if(rebuild)
			paths.clear();
		
		if(paths.isEmpty())
		{
			int[] colors = new int[] { Color.GREEN, Color.MAGENTA, Color.CYAN,Color.DKGRAY, Color.BLUE };
			for (int j = 0; j < items.size(); j++) {
				PolylineOptions polylineOpt = new PolylineOptions().color(colors[j % 5]).width(5).geodesic(false);
				DeliveryItem item = items.get(j);
				/*
				 * For the time beingwe just check whether the item contains or not
				 * the points we want to display... it is better to load them
				 */
				//saqib: if the map view is selected and there is any view on top of it i.e. notification activity, and change the orientation alot then app crashes 
				if((getActivity() != null) && (getActivity().getApplication() != null))
				{
					BikePonyApplication application = (BikePonyApplication) getActivity().getApplication();
					List<String> encodedPath = application.getMapEncodedPath(item.getId());
					
					for(String encStep:encodedPath)
					{
						List<LatLng> points = PolyUtil.decode(encStep);
						polylineOpt.addAll(points);				
					}
					paths.add(polylineOpt);
				}
								
			}
		}

	}
    private void showPath()
    {
		for(PolylineOptions opt :paths)
			map.addPolyline(opt);
    }
    private void showCurrentPositionMarker()
    {
    	if(currentPositionMarker!=null)
    		map.addMarker(currentPositionMarker);
    }
	private void createCurrentPositionMarker() {
		if (lastCurrentPosition == null)
			return;

		if (currentPositionMarker == null) {
			mBubbleIconFactory.setStyle(BubbleIconFactory.Style.BLUE);
			Bitmap bitmap = mBubbleIconFactory.makeIcon("you");
			currentPositionMarker = new MarkerOptions().position(
					lastCurrentPosition).icon(
					BitmapDescriptorFactory.fromBitmap(bitmap));

		} else
			currentPositionMarker.position(lastCurrentPosition);
	}

	@Override
	public void serviceBound(ServerCommunicationService service) {
		this.service = service;
		this.service.registerLocationObserver(this);
	}

	@Override
	public void serviceDisconnected() {
		service.unregisterLocationObserver(this);
	}
	
	
	private class MapsUpdateAsyncTask extends AsyncTask<Void, Void, Boolean>
	{
		private List<DeliveryItem> items;
		private int itemSelected;
		private boolean dirty;
		private LatLng mapCenter;
		private int zoom =13;
		public MapsUpdateAsyncTask(List<DeliveryItem> items, int itemSelected,boolean dirty) {
			this.items=items;
			this.itemSelected=itemSelected;
			this.dirty=dirty;
		}
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			
		}
		@Override
		protected Boolean doInBackground(Void... params) {
			mapCenter=null;
			if(itemSelected!=-1)
			{
				mapCenter =new LatLng(items.get(itemSelected).getLatitutde(), items.get(itemSelected).getLongitude());
				zoom= 15;
			}else if(lastCurrentPosition!=null && followAgentPosition)
			{
				mapCenter=lastCurrentPosition;
			}
			createCurrentPositionMarker();
			if(items!=null)
			{
				prepareMarkers(mapCenter,zoom,itemSelected,items,dirty);
				updateMap(items,dirty);
				return true;
			}
			return false;
		}
		
		@Override
		protected void onPostExecute(Boolean result) {
			if(result)
			{
				showCurrentPositionMarker();
				showMarkers(mapCenter,zoom ,itemSelected, items, dirty);
				showPath();
			}
		}
	}


	@Override
	public boolean onMarkerClick(Marker marker) {
		
		//TODO get the clicked Marker info and behave as expected
		//return false in order to not stop the propagation of the event, and let the InfoWindow to be opened
		Integer selectedItemMarker = markerItemsMap.get(marker.getId());
		deliveriesFacade.onItemSelected(selectedItemMarker);
		return false;
	}
}
